package cs236369.hw5.db;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;

import cs236369.hw5.db.beans.DbEvent;
import cs236369.hw5.db.beans.DbMessage;
import cs236369.hw5.db.beans.DbUser;
import cs236369.hw5.db.types.DbMsgType;

public class DbHandlerImp implements DbHandler
{

	@Override
	public void deleteUser(String username)
	{
		Logger logger = Logger.getLogger(getClass());
		Session session = null;
		try
		{
			logger.info("getting a session from connection pool.");
			session = HibernateUtil.getSessionFactory().getCurrentSession();
			logger.info("begin transactions.");
			session.beginTransaction();
			logger.info("checking that user doesn't exists..");
			DbUser u = (DbUser)session.get(DbUser.class, username);
			if (null != u)
			{
				logger.info("deleting username.." + u.getUsername());
				session.delete(u);
				session.getTransaction().commit();
				logger.info("[OK] user was deleted.");
			}
			else
			{
				logger.info("[FAIL] cant delete user..");
				session.getTransaction().rollback();
			}
				
		}
		catch (Exception e)
		{
			logger.error("",e);
			rollbackSession(session);
		}
	}
	@Override
	public void deleteUserMessage(String username, Long msgId)
	{
		Logger logger = Logger.getLogger(getClass());
		Session session = null;
		try
		{
			logger.info("getting a session from connection pool.");
			session = HibernateUtil.getSessionFactory().getCurrentSession();
			logger.info("begin transactions.");
			session.beginTransaction();
			logger.info("checking that user doesn't exists..");
			DbMessage m = (DbMessage) session.get(DbMessage.class, msgId);
			session.delete(m);
			session.getTransaction().commit();
			logger.info("[OK] msg was deleted.");
		}
		catch (Exception e)
		{
			logger.error("",e);
			rollbackSession(session);
		}
	}
	@Override
	public void saveNewUser(DbUser user) throws Exception
	{
		Logger logger = Logger.getLogger(getClass());
		Session session = null;
		try
		{
			logger.info("getting a session from connection pool.");
			session = HibernateUtil.getSessionFactory().getCurrentSession();
			logger.info("begin transactions.");
			session.beginTransaction();
			logger.info("checking that user doesn't exists..");
			Criteria criteria = session.createCriteria(DbUser.class);
			criteria.add(Restrictions.eq(DbUser.USERNAME, user.getUsername()));
			criteria.setProjection(Projections.rowCount()).uniqueResult();
			Long n = (Long) criteria.uniqueResult();
			if (n > 0)
			{
				logger.info("[FAIL] user exists.");
				session.getTransaction().commit();
				throw new UserRegistrationException("username already exists");
			}
			logger.info("user doesn't exists, adding new user");
			session.save(user);
			session.getTransaction().commit();
			logger.info("[OK] user: " + user.getUsername() + " was added to the db.");
		}
		catch (UserRegistrationException e)
		{
			throw e;
		}
		catch (Exception e)
		{
			rollbackSession(session);
			throw e;
		}

	}

	@Override
	public void save(DbBean o) throws Exception
	{
		Logger logger = Logger.getLogger(getClass());
		Session session = null;
		try
		{
			logger.info("getting a session from connection pool.");
			session = HibernateUtil.getSessionFactory().getCurrentSession();
			logger.info("begin transactions.");
			session.beginTransaction();
			session.saveOrUpdate(o);
			session.getTransaction().commit();
		}
		catch (Exception e)
		{
			rollbackSession(session);
			throw e;
		}
	}

	@Override
	public void saveOrUpdate(DbBean o) throws Exception
	{
		Logger logger = Logger.getLogger(getClass());
		Session session = null;
		try
		{
			logger.info("getting a session from connection pool.");
			session = HibernateUtil.getSessionFactory().getCurrentSession();
			logger.info("begin transactions.");
			session.beginTransaction();
			session.saveOrUpdate(o);
			session.getTransaction().commit();
		}
		catch (Exception e)
		{
			rollbackSession(session);
			throw e;
		}
	}

	@Override
	public Collection<DbMessage> getMessagesForPresenting()
	{
		Logger logger = Logger.getLogger(getClass());
		Session session = null;
		try
		{
			logger.info("getting a session from connection pool.");
			session = HibernateUtil.getSessionFactory().getCurrentSession();
			logger.info("begin transactions.");
			session.beginTransaction();

			Criteria criteria = session.createCriteria(DbMessage.class);
			criteria.addOrder(Order.desc(DbMessage.DATE));

			@SuppressWarnings("unchecked")
			Collection<DbMessage> res = criteria.list();
			if (null == res)
			{
				logger.info("criteria didn't result anything, closeing the session");
				session.getTransaction().commit();
				return new LinkedList<DbMessage>();
			}
			logger.info("loading msgs lazy things.");
			for (DbMessage m : res)
			{
				logger.info("loading msg events: " + m.getId());
				m.getEvents();
				logger.info("loading msg user: " + m.getId());
				m.getUser();
			}
			logger.info("closing session with commit.");
			session.getTransaction().commit();

		}
		catch (Exception e)
		{
			rollbackSession(session);
		}
		return null;
	}

	@Override
	public Collection<DbMessage> find(double x, double y, double d)
	{
		Logger logger = Logger.getLogger(getClass());
		Session session = null;
		try
		{
			logger.info("getting a session from connection pool.");
			session = HibernateUtil.getSessionFactory().getCurrentSession();
			logger.info("begin transactions.");
			session.beginTransaction();

			Query q = session.createQuery("from DbMessage where (x - :x1)*(x - :x2) +(y - :y1)*(y - :y2) < :radius");
			q.setParameter("x1", x);
			q.setParameter("x2", x);
			q.setParameter("y1", y);
			q.setParameter("y2", y);
			q.setParameter("radius", d*d);
			@SuppressWarnings("unchecked")
			List<DbMessage> res = q.list();
			//lazy loading...
			for (DbMessage m : res)
			{
				m.getUser().getUsername();
				if (m.getType() == DbMsgType.event)
				{
					m.getEvents().size();
				}
			}
			session.getTransaction().commit();
			return res;

		}
		catch (Exception e)
		{
			rollbackSession(session);
		}
		return new LinkedList<DbMessage>();
	}

	@Override
	public DbUser getUserProfile(String userName)
	{
		Logger logger = Logger.getLogger(getClass());
		Session session = null;
		try
		{
			logger.info("getting a session from connection pool.");
			session = HibernateUtil.getSessionFactory().getCurrentSession();
			logger.info("begin transactions.");
			session.beginTransaction();
			DbUser user = (DbUser) session.get(DbUser.class, userName);
			// lazy loading of what needed....
			user.getAddress().getId();
			for (DbMessage m : user.getMsgs())
			{
				if (m.getType() == DbMsgType.event)
				{
					m.getEvents().size();
				}
			}
			user.getRoles().size();
			session.getTransaction().commit();
			return user;

		}
		catch (Exception e)
		{
			rollbackSession(session);
		}
		return null;

	}

	@Override
	public boolean registerEvent(String username, Long eventId)
	{
		Logger logger = Logger.getLogger(getClass());
		Session s = null;
		try
		{
			s = HibernateUtil.getSessionFactory().getCurrentSession();
			s.beginTransaction();
			DbEvent event = (DbEvent) s.get(DbEvent.class, eventId, LockMode.UPGRADE_NOWAIT);
			if (event.getRegistered() < event.getCapacity())
			{
				event.registerUser(username);
				s.saveOrUpdate(event);
			}
			s.getTransaction().commit();
			return true;
		}
		catch (Exception e)
		{
			logger.error("",e);
			rollbackSession(s);
		}
		return false;
	}
	
	@Override
	public boolean unRegisterEvent(String username, Long eventId)
	{
		Session s = null;
		try
		{
			s = HibernateUtil.getSessionFactory().getCurrentSession();
			s.beginTransaction();
			DbEvent event = (DbEvent) s.get(DbEvent.class, eventId, LockMode.UPGRADE_NOWAIT);
			event.unRegisterUser(username);
			s.saveOrUpdate(event);
			s.getTransaction().commit();
			return true;
		}
		catch (Exception e)
		{
			rollbackSession(s);
		}
		return false;
	}

	/**
	 * will try to rollback the session.
	 * 
	 * @param s
	 */
	private void rollbackSession(Session s)
	{
		Logger l = Logger.getLogger(getClass());
		l.info("trying to rollback session");
		try
		{
			if (null != s)
			{
				s.getTransaction().rollback();
				l.info("[OK] session was rolledback");
			}
			else
			{
				l.info("[OK] session was null");
			}
		}
		catch (Exception e)
		{
			l.fatal("[FAIL] can't rollback session", e);
		}
	}

}
